﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Windows;
using System.Windows.Media.Animation;
using System.Diagnostics;
using System.Windows.Threading;
using Xceed.Silverlight.Compatibility;

namespace Xceed.Silverlight.DataGrid
{
  public partial class DataGridControl : IManageColumnsMeasure, IManageRootMeasure
  {

    private bool HasPendingUpdate
    {
      get
      {
        return m_rowUpdateManager.HasPendingRequest || m_columnUpdateManager.HasPendingRequest;
      }
    }

    private void SetColumns( UpdateRequest updateInfo )
    {
      m_columnUpdateManager.Update( updateInfo );
    }

    private void SetData( UpdateRequest updateInfo )
    {
      m_rowUpdateManager.Update( updateInfo );
    }

    private Size OnColumnPanelMeasureOverride( Size availableSize )
    {
      UpdateRequest pendingUpdate = m_columnUpdateManager.PendingRequest;

      if( pendingUpdate != null )
      {

        this.EndStoryboard();
      }

      Size measuredSize = m_columnUpdateManager.OnContainerMeasureOverride( availableSize );
      bool supportAnimation = object.Equals( this.AnimationSettings, DataGridAnimationSettings.Default );

      if( pendingUpdate != null && pendingUpdate.UseTransition && supportAnimation )
      {
        TransitionContext transitionContext = this.GetTransitionContext(null);
        this.DataGridPanel.PendingTransitionContext = transitionContext;
      }

      this.DispatchUpdateDisplay( 0, ( pendingUpdate != null ), false );

      return measuredSize;
    }

    private Size OnRootContainerMeasureOverride( Size availableSize )
    {
      UpdateRequest pendingUpdate = m_rowUpdateManager.PendingRequest;
      bool storyboardStarted = ( m_transitionContext != null ) && m_transitionContext.StoryboardStarted;
      if( pendingUpdate == null && !storyboardStarted )
      {
        m_rowUpdateManager.CommitPendingLoad();
        pendingUpdate = m_rowUpdateManager.PendingRequest;
      }

      bool supportAnimation = object.Equals(this.AnimationSettings, DataGridAnimationSettings.Default);
      bool useTransition = false;
      bool shouldInitialize = false;
      bool reverseInitialization = m_reverseInitializationDirection;
      int dispatchUpdateDelay = 0;

      if( pendingUpdate != null )
      {
        useTransition = pendingUpdate.UseTransition && supportAnimation;
        if( pendingUpdate is ScrollUpdateRequest )
        {
          ScrollUpdateRequest scrollUpdate = (ScrollUpdateRequest) pendingUpdate;
          reverseInitialization = scrollUpdate.EventType.IsDecrement()
                                  || (scrollUpdate.EventType.IsThumbTrack() && scrollUpdate.Count < 0);
        }


        shouldInitialize = pendingUpdate.ShouldInitialize;

        // Do not start the storyboard yet, just dispatch it.
        // For immediate display actions, hold the storyboard startup
        // for about 70 ms. This is the time between two "continous clicks"
        // on the scrollbar when the user keeps the mouse pressed on the
        // line down button.
        if( shouldInitialize )
        {
          dispatchUpdateDelay = 70;
        }

        this.EndStoryboard();
      }

      // This will apply any pending updates
      Size measuredSize = m_rowUpdateManager.OnContainerMeasureOverride( availableSize );

      if( useTransition )
      {
        TransitionContext transitionContext = this.GetTransitionContext( pendingUpdate );
        this.RootHost.PendingTransitionContext = transitionContext;
      }

      if( shouldInitialize )
      {
        this.InitializeDisplay( double.MaxValue );
      }

      this.DispatchUpdateDisplay( dispatchUpdateDelay, ( pendingUpdate != null ), reverseInitialization );

      return measuredSize;
    }

    private void RefreshColumns( bool useTransition )
    {
      if( this.IsInitializing )
        return;

      this.SetColumns( new UpdateRequest( useTransition ) );
    }

    private void RefreshData( bool useTransition )
    {
      if( useTransition )
      {
        this.SetData( new UpdateRequest( true ) );
      }
      else
      {
        UpdateRequest refresh = new UpdateRequest( false );
        refresh.UpdateDetails.RowPreload = true;
        this.SetData( refresh );
      }
    }

    private void UpdateDisplay()
    {
      if( this.HasPendingUpdate )
        return;

      if( ( m_transitionContext != null ) && !m_transitionContext.StoryboardStarted )
      {
        this.StartStoryboard();
        this.DispatchUpdateDisplay( 0, false, m_reverseInitializationDirection );
        return;
      }

      if( this.InitializeDisplayItems() )
        return;
    }

    private bool InitializeDisplayItems()
    {
      if( m_transitionContext != null )
        //Just post pone the initialization when the animation is done.
        return true;

      //Start applying data context to display cells content
      bool hasInitialized;
      bool needMoreInitialization;
      int initWeight = Convert.ToInt32( Math.Floor( m_initializationWeight ) );
      this.InitializeDisplay( initWeight, m_reverseInitializationDirection,  out hasInitialized, out needMoreInitialization );
      if( needMoreInitialization )
      {
        // There are more items to initialize.
        // Increase the number of items to initialize on the next call.
        m_initializationWeight *= ( 1d + DataContextInitializerVisitor.InitializationIncrementFactor );
        this.DispatchUpdateDisplay( 0, false, m_reverseInitializationDirection );
        return true;
      }

      m_reverseInitializationDirection = false;



      return false;
    }

    private TransitionContext GetTransitionContext( UpdateRequest updateRequest )
    {
      if( m_transitionContext == null )
      {
        bool isLineOpration = ( updateRequest is ScrollUpdateRequest ) && !( ( ScrollUpdateRequest )updateRequest ).IsLargeChange;
        int animationDuration = ( isLineOpration ) ? DataGridControl.LineDownAnimationDuration : DataGridControl.AnimationDuration;

        Storyboard currentStoryboard = new Storyboard();
        EasingFunctionBase ease = new PowerEase { Power = 3, EasingMode = EasingMode.EaseOut };
        currentStoryboard.Completed += Storyboard_Completed;
        currentStoryboard.Duration = new Duration( TimeSpan.FromMilliseconds( animationDuration ) );
        m_transitionContext = new TransitionContext( currentStoryboard, ease );
      }

      return m_transitionContext;
    }

    private void OnDataGridPanelLayoutCompleted( int displatchDelay )
    {
      this.DataGridPanel.LayoutUpdated -= m_layoutCompletedHandler;
      m_layoutCompletedHandler = null;

      if( displatchDelay > 0 )
      {
        m_timer.Interval = new TimeSpan( 0, 0, 0, 0, displatchDelay );
        m_timer.Start();
      }
      else
      {
        long dispatchId = System.Environment.TickCount;
        m_dispatchId = dispatchId;
        this.Dispatcher.BeginInvoke(
          () => 
                       {
                         if (m_dispatchId == dispatchId)
                         {
                           this.OnTimerTick(m_timer, EventArgs.Empty);
                         }
            } );
      }
    }

    private void DispatchUpdateDisplay( int milisecondDelay, bool newUpdateApplied, bool reverseInitialization )
    {
      if( m_layoutCompletedHandler != null )
      {
        this.DataGridPanel.LayoutUpdated -= m_layoutCompletedHandler;
        m_layoutCompletedHandler = null;
      }

      if( newUpdateApplied )
      {
        // On each applied update, restart initialisation with only 1 item.
        m_initializationWeight = DataContextInitializerVisitor.InitialInitializationWeight;
      }

      m_reverseInitializationDirection = reverseInitialization;

      m_timer.Stop();

      m_layoutCompletedHandler = (s, args) => 
        this.OnDataGridPanelLayoutCompleted(milisecondDelay);

      this.DataGridPanel.LayoutUpdated += m_layoutCompletedHandler;
    }

    private void OnTimerTick( object sender, EventArgs e )
    {
      m_timer.Stop();
      this.UpdateDisplay();
    }

    private void InitializeDisplay( double maxInitializationWeight )
    {
      bool notUsed1;
      bool notUsed2;
      this.InitializeDisplay( maxInitializationWeight, m_reverseInitializationDirection, out notUsed1, out notUsed2 );
    }

    private void InitializeDisplay(double maxInitializationWeight, bool reverseInitialization, out bool hasInitialized, out bool needMoreInitialize )
    {
      DataContextInitializerVisitor visitor = new DataContextInitializerVisitor( this, maxInitializationWeight, reverseInitialization );
      this.RootContainer.Accept( visitor );
      hasInitialized = visitor.InitializationOccured;
      needMoreInitialize = visitor.NeedMoreInitialization;
    }

    private void Storyboard_Completed( object sender, EventArgs e )
    {
      this.EndStoryboard();
      Debug.Assert(!m_rowUpdateManager.HasPendingRequest);
      m_rowUpdateManager.CommitPendingLoad();

      this.UpdateDisplay();
    }

    private void StartStoryboard()
    {
      m_transitionContext.StartStoryboard();
    }

    private void EndStoryboard()
    {
      if( m_transitionContext != null )
      {
        m_transitionContext.Storyboard.Completed -= Storyboard_Completed;
        m_transitionContext.Storyboard.Stop();
        m_transitionContext = null;
        //End transition
        this.DataGridPanel.ColumnPanel.EndTransition();
        this.DataGridPanel.Accept( new CleanUpVisitor( this ) );
      }
    }

    private const int AnimationDuration = 500;
    private const int LineDownAnimationDuration = 200;

    private DispatcherTimer m_timer;
    private TransitionContext m_transitionContext;
    private EventHandler m_layoutCompletedHandler;
    private double m_initializationWeight = DataContextInitializerVisitor.InitialInitializationWeight;
    private bool m_reverseInitializationDirection = false;
    private long m_dispatchId;


    #region IManageColumnsMeasure Members

    Size IManageColumnsMeasure.OnMeasureOverride( Size availableSize )
    {
      return this.OnColumnPanelMeasureOverride( availableSize );
    }

    #endregion

    #region IManageRootMeasure Members

    Size IManageRootMeasure.OnMeasureOverride( Size availableSize )
    {
      return this.OnRootContainerMeasureOverride( availableSize );
    }

    #endregion
  }
}
